/*
 *  Copyright 2009 Advanced Industrial Science and Technology (AIST).
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/*! \file
 *  \brief
 */
#pragma once

#include <thrust/mpl/if.hpp>
#include <thrust/mpl/bool.hpp>

#include <thrust/graph/detail/out_edge_iterator_base.hpp>

#include <thrust/iterator/iterator_traits.h>
#include <thrust/iterator/iterator_adaptor.h>

#include <thrust/device_new.h>

//#include <thrust/detail/dispatch/copy.h>

namespace thrust {

namespace detail {

struct out_edge_iter_friend;
struct in_edge_iter_friend;
  
} // end namespace detail

template <typename OutEdgeIter // OutEdgeIter is device_ptr<StoredEdge>
          , typename VertexDescriptor
          , typename EdgeDescriptor
          , typename CalledSpace>
struct out_edge_iter
  : public detail::out_edge_iter_base<OutEdgeIter
                                      , VertexDescriptor
                                      , EdgeDescriptor
                                      , CalledSpace
                                      >::type
{
  
public:
  typedef CalledSpace called_space;
  
private:
  typedef out_edge_iter<OutEdgeIter
                        , VertexDescriptor
                        , EdgeDescriptor
                        , CalledSpace
                        > self_t;
  
  typedef typename detail::out_edge_iter_base<OutEdgeIter
                                              , VertexDescriptor
                                              , EdgeDescriptor
                                              , CalledSpace
                                              >::type super_t;
  
  friend class experimental::iterator_core_access;
  friend struct detail::out_edge_iter_friend;
  
private:
  typedef typename iterator_traits<OutEdgeIter>::value_type StoredEdge;
  
  
public:
  VertexDescriptor m_src;
  //EdgeDescriptor m_edge;
  
public:
  __host__ __device__
  inline out_edge_iter() {}
  
  __host__ __device__
  inline out_edge_iter( const OutEdgeIter &iter, const VertexDescriptor& src)
    : super_t(iter), m_src(src) {}
  
public:
  __device__
  inline const VertexDescriptor&
  _target() const {
    // this->base() is device_ptr<StoredEdge>
    return this->base().get()->m_target;
  }
  
private:
  
  __host__ __device__
  inline EdgeDescriptor
  dereference() const {
    return dereference( CalledSpace());
  }
  
  __host__
  inline EdgeDescriptor
  dereference( thrust::called_from_host_tag) const {
    // this->base is OutEdgeIter =device_ptr<StoredEdge>
    const OutEdgeIter& iter =this->base();
    StoredEdge se; // se is on host
    thrust::copy( iter, iter+1, &se);
    return EdgeDescriptor( m_src, se.get_target(), se.get_property());
  }
  
    __device__
    inline EdgeDescriptor
    dereference( thrust::called_from_device_tag) const {
      //this->base is OutEdgeIter = device_ptr<StoredEdge>
      const OutEdgeIter& iter =this->base(); 
      return EdgeDescriptor( m_src, iter.get()->get_target(), iter.get()->get_property());
    }

}; // end out_edge_iter
  
template <typename InEdgeIter
          , typename VertexDescriptor
          , typename EdgeDescriptor
          , typename CalledSpace>
struct in_edge_iter
  : public detail::in_edge_iter_base<InEdgeIter
                                     , VertexDescriptor
                                     , EdgeDescriptor
                                     , CalledSpace
                                     >::type
{
private:
  typedef in_edge_iter<InEdgeIter
                       , VertexDescriptor
                       , EdgeDescriptor
                       , CalledSpace
                       > self_t;
  
  typedef typename detail::in_edge_iter_base<InEdgeIter
                                             , VertexDescriptor
                                             , EdgeDescriptor
                                             , CalledSpace
                                             >::type super_t;
  
  friend class experimental::iterator_core_access;
  friend struct detail::in_edge_iter_friend;
  
private:
  typedef typename iterator_traits<InEdgeIter>::value_type StoredEdge;
  
public:
  VertexDescriptor m_target;
  
public:
  __host__ __device__
  inline in_edge_iter() {}
  
  __host__ __device__
  inline in_edge_iter( const InEdgeIter &iter, const VertexDescriptor& target)
    : super_t(iter), m_target(target) {}
  
  /*__host__ __device__
    inline self_t& operator=( const self_t& x) {
    //return *this;
    }*/
  
private:
  __host__ __device__
  inline EdgeDescriptor
  dereference() const 
  {
    return dereference( CalledSpace());
  }
  
  __host__
  inline EdgeDescriptor
  dereference( thrust::called_from_host_tag) const {
    const InEdgeIter& iter =this->base();
    StoredEdge se;
    thrust::copy( iter, iter+1, &se);
    return EdgeDescriptor( se.get_target(), m_target);
  }
  
  __device__
  inline EdgeDescriptor
  dereference( thrust::called_from_device_tag) const {
      const InEdgeIter& iter =this->base();
      return EdgeDescriptor( iter.get()->get_target(), m_target, iter.get()->get_property());
  }
};
  
} // end namespace thrust

#include <thrust/graph/detail/out_edge_iterator.inl>
